package ratkit.repast;

import java.io.File;

import ratkit.repast.schedule.RatKitRunEnvironmentBuilder;
import ratkit.repast.schedule.RatKitSchedule;
import repast.simphony.batch.BatchScenarioLoader;
import repast.simphony.context.Context;
import repast.simphony.engine.controller.Controller;
import repast.simphony.engine.controller.DefaultController;
import repast.simphony.engine.environment.AbstractRunner;
import repast.simphony.engine.environment.ControllerRegistry;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.environment.RunEnvironmentBuilder;
import repast.simphony.engine.environment.RunState;
import repast.simphony.engine.schedule.ISchedule;
import repast.simphony.engine.schedule.Schedule;
import repast.simphony.engine.schedule.ScheduleParameters;
import repast.simphony.parameter.SweeperProducer;
import simphony.util.messages.MessageCenter;

/**
 * This class executes the test using Repast simulation suite capabilities.
 * 
 * 
 * @author Önder Gürcan, İbrahim Çakırlar
 * @version 1.0 14.01.2013
 * 
 */
public class RepastRunner extends AbstractRunner {

	/**
	 * Message center of the runner.
	 */
	private static MessageCenter msgCenter = MessageCenter
			.getMessageCenter(RepastRunner.class);
	/**
	 * Run environment builder for the test caes.
	 */
	private RunEnvironmentBuilder runEnvironmentBuilder;
	/**
	 * Controller instance of the test environment.
	 */
	private Controller controller;

	/**
	 * Execution field for the runner.
	 */
	protected boolean pause = false;
	/**
	 * Monitor object for concurrent execution.
	 */
	protected Object monitor = new Object();
	/**
	 * Sweeper producer for the test runner.
	 */
	protected SweeperProducer producer;
	/**
	 * Schedule instance for the test runner.
	 */
	private ISchedule schedule;
	/**
	 * Scenario loader for the test runner.
	 */
	private BatchScenarioLoader loader;

	/**
	 * Creates a new runner and associates the run environment with this runner
	 * instance.
	 */
	public RepastRunner() {
		this.setRunEnvironmentBuilder(new RatKitRunEnvironmentBuilder(this,
				true));
		this.setController(new DefaultController(this
				.getRunEnvironmentBuilder()));
		this.getController().setScheduleRunner(this);
	}

	/**
	 * Loads the required files lke user_path.xml, parameters.xml ...etc in the
	 * given directory
	 * 
	 * @param scenarioDirectory
	 *            directory of the secenario files.
	 * @throws Exception
	 */
	public void loadScenario(File scenarioDirectory) throws Exception {
		// load file if exists...
		if (scenarioDirectory.exists()) {
			// initialize loader and define register...
			this.setLoader(new BatchScenarioLoader(scenarioDirectory));
			ControllerRegistry registry = this.getLoader().load(
					this.getRunEnvironmentBuilder());
			this.getController().setControllerRegistry(registry);
			this.getController().batchInitialize();
			// set parameters...
			this.getController().runParameterSetters(
					this.getLoader().getParameters());
		} else {
			msgCenter.error("Scenario not found", new IllegalArgumentException(
					"Invalid scenario " + scenarioDirectory.getAbsolutePath()));
			System.out.println("Scenario not found");
			return;
		}
	}

	/**
	 * initialize the controller with defined parameters and associate schedule
	 * with current {@code Schedule} instance.
	 */
	public void runInitialize() {
		this.getController().runInitialize(this.getLoader().getParameters());
		setSchedule(RunState.getInstance().getScheduleRegistry()
				.getModelSchedule());
	}

	/**
	 * Clean controller. Generally executed after each test method run.
	 */
	public void cleanUpRun() {
		this.getController().runCleanup();
	}

	/**
	 * Cleans batch.
	 */
	public void cleanUpBatch() {
		this.getController().batchCleanup();
	}

	/**
	 * @return the tick count of the next scheduled item
	 */
	public double getNextScheduledTime() {
		return ((Schedule) RunEnvironment.getInstance().getCurrentSchedule())
				.peekNextAction().getNextTime();
	}

	/**
	 * @return the number of model actions on the schedule
	 */
	public int getModelActionCount() {
		return getSchedule().getModelActionCount();
	}

	/**
	 * @return the number of non-model actions on the schedule
	 */
	public int getActionCount() {
		return getSchedule().getActionCount();
	}

	/**
	 * Gets the current master {@code Context} instance of the test environment.
	 * 
	 * @return context instance.
	 */
	@SuppressWarnings("rawtypes")
	public Context getContext() {
		return this.getController().getCurrentRunState().getMasterContext();
	}

	/**
	 * Steps the test scenario one step (one tick).
	 * 
	 * @see repast.simphony.engine.environment.AbstractRunner#step()
	 */
	public void step() {
		getSchedule().execute();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see repast.simphony.engine.environment.AbstractRunner#stop()
	 */
	public void stop() {
		super.stop();
		if (getSchedule() != null)
			getSchedule().executeEndActions();
	}

	/**
	 * Sets the schedule for the finish.
	 * 
	 * @param finishing
	 *            value of the schedule finishing to set.
	 */
	public void setFinishing(boolean finishing) {
		getSchedule().setFinishing(finishing);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see repast.simphony.engine.graph.Executor#execute(java.lang.Object)
	 */
	public void execute(RunState toExecuteOn) {
		// required AbstractRunner stub. We will control the
		// schedule directly.
	}

	/**
	 * Schedules the given method of the given target with given schedule
	 * parameters. This method is only used while executing test methods.
	 * 
	 * @param params
	 *            schedule parameters of the current method.
	 * @param target
	 *            test agent instance.
	 * @param methodName
	 *            test method name to schedule.
	 */
	public void schedule(ScheduleParameters params, Object target,
			String methodName) {
		// get current schedule instance..
		ISchedule mySchedule = getSchedule();
		// schedule the test method...
		((RatKitSchedule) mySchedule).createAction(params, target, methodName,
				new Object[0]);
	}

	/**
	 * Schedules the given agent instance.
	 * 
	 * @param target
	 *            test agent instance.
	 * 
	 */
	public void schedule(Object target) {
		// get current schedule instance..
		ISchedule mySchedule = getSchedule();
		// schedule the test method...
		((RatKitSchedule) mySchedule).schedule(target, new Object[0]);
	}

	/**
	 * Sets the runner schedule.
	 * 
	 * @param schedule
	 *            schedule to set.
	 */
	public void setSchedule(ISchedule schedule) {
		this.schedule = schedule;
	}

	/**
	 * @return the current schedule of the runner.
	 */
	public ISchedule getSchedule() {
		return schedule;
	}

	public void setRunEnvironmentBuilder(
			RunEnvironmentBuilder runEnvironmentBuilder) {
		this.runEnvironmentBuilder = runEnvironmentBuilder;
	}

	public RunEnvironmentBuilder getRunEnvironmentBuilder() {
		return runEnvironmentBuilder;
	}

	public Controller getController() {
		return controller;
	}

	public void setController(Controller controller) {
		this.controller = controller;
	}

	public BatchScenarioLoader getLoader() {
		return loader;
	}

	public void setLoader(BatchScenarioLoader loader) {
		this.loader = loader;
	}
}